﻿using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Runtime.InteropServices;
using Microsoft.Research.Kinect.Nui;
using Coding4Fun.Kinect.Wpf;

using DPoint = System.Drawing.Point;

namespace Mouse_and_Skyrim_Kinect
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private const float SkeletonMaxX = 0.20f;
        private const float SkeletonMaxY = 0.10f;



        private Runtime nui;
        private Camera cam;
        private int totalFrames = 0;
        private int lastFrames = 0;
        private DateTime lastTime = DateTime.MaxValue;
        private SkyrimControl skyrimControl;
        PresentationSource ActiveSource;

        private Dictionary<JointID, Brush> jointColours = new Dictionary<JointID, Brush>()
    {
        { JointID.HipCenter, new SolidColorBrush(Color.FromRgb(169, 176, 155)) },
        { JointID.Spine, new SolidColorBrush(Color.FromRgb(169, 176, 155)) },
        { JointID.ShoulderCenter, new SolidColorBrush(Color.FromRgb(168, 230, 29)) },
        { JointID.Head, new SolidColorBrush(Color.FromRgb(200, 0,   0)) },
        { JointID.ShoulderLeft, new SolidColorBrush(Color.FromRgb(79,  84,  33)) },
        { JointID.ElbowLeft, new SolidColorBrush(Color.FromRgb(84,  33,  42)) },
        { JointID.WristLeft, new SolidColorBrush(Color.FromRgb(255, 126, 0)) },
        { JointID.HandLeft, new SolidColorBrush(Color.FromRgb(215,  86, 0)) },
        { JointID.ShoulderRight, new SolidColorBrush(Color.FromRgb(33,  79,  84)) },
        { JointID.ElbowRight, new SolidColorBrush(Color.FromRgb(33,  33,  84)) },
        { JointID.WristRight, new SolidColorBrush(Color.FromRgb(77,  109, 243)) },
        { JointID.HandRight, new SolidColorBrush(Color.FromRgb(37,   69, 243)) },
        { JointID.HipLeft, new SolidColorBrush(Color.FromRgb(77,  109, 243)) },
        { JointID.KneeLeft, new SolidColorBrush(Color.FromRgb(69,  33,  84)) },
        { JointID.AnkleLeft, new SolidColorBrush(Color.FromRgb(229, 170, 122)) },
        { JointID.FootLeft, new SolidColorBrush(Color.FromRgb(255, 126, 0)) },
        { JointID.HipRight, new SolidColorBrush(Color.FromRgb(181, 165, 213)) },
        { JointID.KneeRight, new SolidColorBrush(Color.FromRgb(71, 222,  76)) },
        { JointID.AnkleRight, new SolidColorBrush(Color.FromRgb(245, 228, 156)) },
        { JointID.FootRight, new SolidColorBrush(Color.FromRgb(77,  109, 243)) }
    };

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            this.nui = Runtime.Kinects[0];

            ActiveSource = PresentationSource.FromVisual(this);
            skyrimControl = new SkyrimControl();

            try
            {
                nui.Initialize(RuntimeOptions.UseColor | RuntimeOptions.UseSkeletalTracking | RuntimeOptions.UseDepth);
                this.cam = nui.NuiCamera;
            }
            catch (InvalidOperationException)
            {
                MessageBox.Show("Runtime initialization failed. Ensure Kinect is plugged in");
                return;
            }
            try
            {
                this.nui.VideoStream.Open(ImageStreamType.Video, 2, ImageResolution.Resolution640x480, ImageType.Color);
                //this.nui.DepthStream.Open(ImageStreamType.Depth, 2, ImageResolution.Resolution640x480, ImageType.Depth);
            }
            catch (InvalidOperationException)
            {
                MessageBox.Show("Failed to open stream. Specify a supported image type/resolution.");
                return;
            }

            //Must set to true and set after call to Initialize
            nui.SkeletonEngine.TransformSmooth = true;

            //Use to transform and reduce jitter
            var parameters = new TransformSmoothParameters
            {
                Smoothing = 0.7f,
                Correction = 0.3f,
                Prediction = 0.4f,
                JitterRadius = 1.0f,
                MaxDeviationRadius = 0.05f
            };

            nui.SkeletonEngine.SmoothParameters = parameters;

            this.lastTime = DateTime.Now;
            this.nui.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(nui_SkeletonFrameReady);
        }

        private void nui_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
        {
            SkeletonFrame skeletonFrame = e.SkeletonFrame;
            this.skeleton.Children.Clear();

            // Gets first skeleton (Only tracks one)
            SkeletonData skeletalData = (from s in skeletonFrame.Skeletons
                                            where s.TrackingState == SkeletonTrackingState.Tracked
                                            select s).FirstOrDefault();

            Brush brush = new SolidColorBrush(Color.FromRgb(128, 128, 255));

            if (SkeletonTrackingState.Tracked == skeletalData.TrackingState)
            {
                // Draw bones
                this.skeleton.Children.Add(this.getBodySegment(skeletalData.Joints, brush, JointID.HipCenter, JointID.Spine, JointID.ShoulderCenter, JointID.Head));
                this.skeleton.Children.Add(this.getBodySegment(skeletalData.Joints, brush, JointID.ShoulderCenter, JointID.ShoulderLeft, JointID.ElbowLeft, JointID.WristLeft, JointID.HandLeft));
                this.skeleton.Children.Add(this.getBodySegment(skeletalData.Joints, brush, JointID.ShoulderCenter, JointID.ShoulderRight, JointID.ElbowRight, JointID.WristRight, JointID.HandRight));
                this.skeleton.Children.Add(this.getBodySegment(skeletalData.Joints, brush, JointID.HipCenter, JointID.HipLeft, JointID.KneeLeft, JointID.AnkleLeft, JointID.FootLeft));
                this.skeleton.Children.Add(this.getBodySegment(skeletalData.Joints, brush, JointID.HipCenter, JointID.HipRight, JointID.KneeRight, JointID.AnkleRight, JointID.FootRight));

                var drawJoints = new List<Joint>(){skeletalData.Joints[JointID.HipCenter], skeletalData.Joints[JointID.Spine], skeletalData.Joints[JointID.ShoulderCenter], skeletalData.Joints[JointID.Head], skeletalData.Joints[JointID.ShoulderLeft],
                                            skeletalData.Joints[JointID.ElbowLeft], skeletalData.Joints[JointID.WristLeft], skeletalData.Joints[JointID.HandLeft], skeletalData.Joints[JointID.ShoulderRight], skeletalData.Joints[JointID.ElbowRight], 
                                            skeletalData.Joints[JointID.WristRight], skeletalData.Joints[JointID.HandRight], skeletalData.Joints[JointID.KneeLeft], skeletalData.Joints[JointID.AnkleLeft],skeletalData.Joints[JointID.FootLeft],
                                            skeletalData.Joints[JointID.FootRight], skeletalData.Joints[JointID.KneeRight], skeletalData.Joints[JointID.AnkleRight]};

                // Draw joints
                foreach (Joint joint in drawJoints)
                {
                    Point jointPos = this.getDisplayPosition(joint);
                    Line jointLine = new Line();
                    jointLine.X1 = jointPos.X - 3;
                    jointLine.X2 = jointLine.X1 + 6;
                    jointLine.Y1 = jointLine.Y2 = jointPos.Y;
                    jointLine.Stroke = jointColours[joint.ID];
                    jointLine.StrokeThickness = 6;

                    this.skeleton.Children.Add(jointLine);
                }

                Joint rightHand = skeletalData.Joints[JointID.HandRight];//.ScaleTo((int)SystemParameters.PrimaryScreenWidth, (int)SystemParameters.PrimaryScreenHeight, SkeletonMaxX, SkeletonMaxY);
                Point rightHandPoint = getDisplayPosition(rightHand);
                Point leftHandPoint = getDisplayPosition(skeletalData.Joints[JointID.HandLeft]);

                Process[] skyrim = Process.GetProcessesByName("TESV");

                if (skyrim.Length == 0)
                {
                    MouseControl.UpdateMouse(rightHand, leftHandPoint, SkeletonMaxX, SkeletonMaxY);

                    HandDepth.Text = getJointDepth(rightHand).ToString() + " ft";
                    XPosition.Text = rightHandPoint.X.ToString();
                    YPosition.Text = rightHandPoint.Y.ToString();
                }
                else
                {
                    if (skyrimControl == null)
                        skyrimControl = new SkyrimControl();

                    skyrimControl.SetProccessWindow();

                    skyrimControl.DetermineAction(skeletalData, this.skeleton, this.nui);
                }
            }


            this.CalculateFps();
        }

        private Polyline getBodySegment(JointsCollection joints, Brush brush, params JointID[] ids)
        {
            PointCollection points = new PointCollection(ids.Length);
            for (int i = 0; i < ids.Length; ++i)
            {
                points.Add(this.getDisplayPosition(joints[ids[i]]));
            }
            return new Polyline()
            {
                Points = points,
                Stroke = brush,
                StrokeThickness = 5
            };
        }

        private void CalculateFps()
        {
            this.totalFrames++;
            DateTime current = DateTime.Now;
            if (current.Subtract(this.lastTime) > TimeSpan.FromSeconds(1))
            {
                int frameDifference = this.totalFrames - this.lastFrames;
                this.lastFrames = this.totalFrames;
                this.lastTime = current;
                this.fps.Text = frameDifference.ToString() + " fps";
            }
        }

        private Point getDisplayPosition(Joint joint)
        {
            int colourX, colourY;
            float depthX, depthY;
            this.nui.SkeletonEngine.SkeletonToDepthImage(joint.Position, out depthX, out depthY);
            depthX = Math.Max(0, Math.Min(depthX * 320, 320));
            depthY = Math.Max(0, Math.Min(depthY * 240, 240));
            ImageViewArea imageView = new ImageViewArea();
            this.nui.NuiCamera.GetColorPixelCoordinatesFromDepthPixel(ImageResolution.Resolution640x480, imageView, (int)depthX, (int)depthY, 0, out colourX, out colourY);
            return new Point((int)(skeleton.Width * colourX / 640.0), (int)(skeleton.Height * colourY / 480));
        }

        // Returns joint depth in ft
        private double getJointDepth(Joint joint)
        {
            short depthValue;
            float depthX, depthY;
            this.nui.SkeletonEngine.SkeletonToDepthImage(joint.Position, out depthX, out depthY, out depthValue);

            double depth = depthValue >> 3;
            depth = depth * 0.1 * 0.393700787 / 12;

            return Math.Round(depth, 3);
        }


        private void Window_Closed(object sender, EventArgs e)
        {
            nui.Uninitialize();
            Environment.Exit(0);
        }
    }
}
